#!/usr/bin/python
# -*- encoding: utf-8 -*-

import sys
import re

import link

flags = {
    'use_chinese_bracket': False,
    'ignore_missing_link': False,
}

class CodeParser(object):
    def __init__(self):
        self.code_map = []
        self.newline = '__NL__'
        self.index = 0

    def parse(self, text):
        text = self.code_to_code_id(text)
        return self.restore(text)

    def code_to_code_id(self, text):
        # For each {{{ ... }}}, do the following things:
        # 1. replace it by <pre> ... </pre>.
        # 2. replace <pre> ... </pre> by code id and store (code id, code) in self.code_map.

        lines = []
        in_block = False
        lang_is_parsed = False
        for line in text.split("\n"):
            new_line = self.parse_pass(line)
            if new_line != line:
                lines.append(new_line)
                continue

            if re.search("{{{$", line):
                if in_block:
                    raise SyntaxError("in line: %s\nnested {{{", line)

                in_block = True
                lines.append(line[:-3] + u'<pre line="1" lang="LANG">')
                continue

            if in_block and not lang_is_parsed:
                matched = re.match("#!(.+)$", line)
                if not matched:
                    raise SyntaxError('in line: %s\ncannot find "#!LANGUAGE" after "{{{"' % line)
                assert 'LANG' in lines[-1]
                lines[-1] = lines[-1].replace('LANG', matched.groups()[0])
                lang_is_parsed = True
                continue

            if "}}}" == line:
                in_block = False
                lang_is_parsed = False
                lines.append('</pre>')
                self._replace_code(lines)
                continue

            lines.append(line)
        if in_block:
            raise SyntaxError("miss }}}")

        return "\n".join(lines)

    def parse_pass(self, line):
        while True:
            matched = re.match("(.*){{{(.+?)}}}(.*)", line)
            if not matched:
                break

            pre_str, pass_str, post_str = matched.groups()
            code_id = self.get_code_id()
            self.code_map.append((code_id, pass_str))
            line = pre_str + code_id + post_str

        return line

    def get_code_id(self):
        self.index += 1
        return "__CODE_%d__" % self.index

    def _replace_code(self, lines):
        # Replace <pre ...> ... </pre> to an identifier (e.g. __CODE_N__),
        # and save the pair (identifier, codes) in self.code_map.
        # This way prevent codes from being parsed by other parsing functions.

        for begin in reversed(range(len(lines) - 1)):
            matched = re.search('<pre line="1" lang=', lines[begin])
            if matched:
                break
        assert matched

        # Fold <pre ...> ... </pre> into one line. Otherwise re.match processes only lines[begin]
        code_in_one_line = self.newline.join(lines[begin:])
        matched = re.match('(.*?)(<pre line="1" lang=.*)', code_in_one_line)
        pre_str, code = matched.groups()
        code = CodeParser._to_blockquote_if_necessary(code)

        code_id = self.get_code_id()
        self.code_map.append((code_id, code))
        lines[begin:] = [pre_str + code_id]

    @staticmethod
    def _to_blockquote_if_necessary(line):
        begin_tag = '<pre line="1" lang="quote">'
        end_tag = '</pre>'
        if line[:len(begin_tag)] == begin_tag and line[-len(end_tag):] == end_tag:
            line = line.replace(begin_tag, '<blockquote>')
            line = line.replace(end_tag, '</blockquote>')
        return line

    def restore(self, text):
        '''\
        Restore the codes according to code identifiers.
        The detail of implementation is just doing the inversed operations in _replace_code().
        '''
        for code_id, code in self.code_map:
            text = text.replace(code_id, code)
        return text.replace(self.newline, '\n')


def strip_wiki_code(line, wiki_code_re):
    while True:
        new_line = re.sub(wiki_code_re, "", line)
        if new_line == line:
            break
        line = new_line
    return new_line

def parse_font_style(text):
    lines = []
    for line in text.split("\n"):
        syntaxes = ["'''", "''", "~~", "\\^", ",,"]
        tags = ['strong', 'em', 'del', 'sup', 'sub']
        for syntax, tag in zip(syntaxes, tags):
            for matched in re.findall("%s.+?%s" % (syntax, syntax), line):
                text = strip_wiki_code(matched, syntax)
                line = line.replace(matched, "<%s>%s</%s>" % (tag, text, tag))
        lines.append(line)

    return "\n".join(lines)

def parse_heading(text):
    lines = []
    for line in text.split("\n"):
        if not line or line[0] != '=' or line[-1] != '=':
            lines.append(line)
            continue

        matched = re.match("(=+) ([^=]*) \\1$", line)
        if not matched:
            lines.append(line)
            continue
        eq_str, text = matched.groups()

        n = len(eq_str)
        lines.append(u"<h%d>%s</h%d>" % (n, text, n))

    return "\n".join(lines)

def open_tag2end_tag(tag):
    alist = list(tag)
    alist[1:1] = '/'
    return "".join(alist)

def parse_list(text):
    def set_in_list(in_list, lines, tag=None):
        if in_list:
            lines.append(open_tag2end_tag(set_in_list.tag))
            return False

        set_in_list.tag = '<ol>'
        if tag == '*':
            set_in_list.tag = '<ul>'
        lines.append(set_in_list.tag)
        return True

    lines = []
    in_list = False
    for line in text.split("\n"):
        matched = re.match(" (\*|1.) (.+)", line)
        if not matched:
            if in_list:
                in_list = set_in_list(in_list, lines)
            lines.append(line)
            continue

        list_type, line = matched.groups()
        if not in_list:
            in_list = set_in_list(in_list, lines, list_type)
        lines.append("<li>%s</li>" % line)
    if in_list:
        set_in_list(in_list, lines)

    return "\n".join(lines)

def parse_blockquote(text):
    lines = []
    in_block = False
    for line in text.split("\n"):
        if re.match("  \S+", line):
            if not in_block:
                in_block = True
                lines.append("<blockquote>")

            lines.append(line[2:])
            continue

        if in_block:
            in_block = False
            lines.append("</blockquote>")
        lines.append(line)
    if in_block:
        lines.append("</blockquote>")

    return "\n".join(lines)

def parse_link(text, link_dict=link.get_dict()):
    lbrackets = [u'[']
    rbrackets = [u']']

    if flags['use_chinese_bracket']:
        lbrackets.append(u'〔')
        rbrackets.append(u'〕')

    def parse_hyperlink(line):
        def is_image(link):
            temp = link.split('.')
            if len(temp) < 2:
                return False
            return temp[-1] in ['png', 'gif', 'jpg', 'jpeg', 'bmp']

        pattern = "([%s]?http://\S+)(\s|$)" % ("".join(lbrackets))
        for link, space in re.findall(pattern, line):
            if link[0] in lbrackets:  # Don't parse wiki-link
                continue
            if is_image(link):
                line = line.replace(link, '<img src="%s" />' % link)
                continue
            line = line.replace(link, '<a href="%s">%s</a>' % (link, link))
        return line

    def parse_wikilink(line):
        def backslash(strs):
            return ['\\' + s for s in strs]

        def get_brackets():
            return ('|'.join(backslash(lbrackets)), '|'.join(rbrackets))

        pattern = "((%s).+?(%s))" % get_brackets()  # [...]
        for link, lbracket, rbracket in re.findall(pattern, line):
            pattern = "(%s)(http://\S+|/\S+) +(.+?)(%s)" % get_brackets()  # [http://URL TITLE]
            matched = re.match(pattern, link)
            if matched:
                url, title = matched.groups()[1:3]
                line = line.replace(link, '<a href="%s">%s</a>' % (url, title))
                link_dict[title] = url
                continue

            title = link[1:-1]
            if title not in link_dict:
                message = 'in line: %s\ncannot find any link for title: %s' % (line, title)
                if flags['ignore_missing_link']:
                    print >>sys.stderr, ('WARNING: ' + message + '\n').encode('utf8')
                    continue
                else:
                    raise SyntaxError(message)

            line = line.replace(link, '<a href="%s">%s</a>' % (link_dict[title], title))
        return line

    lines = []
    for line in text.split("\n"):
        line = parse_hyperlink(line)
        line = parse_wikilink(line)
        lines.append(line)

    return "\n".join(lines), link_dict

def parse(text, save_link_dict=True):
    def get_simple_parser():
        self_module = sys.modules[__name__]
        operators = [getattr(self_module, op) for op in dir(self_module) if re.match("parse_.+", op)]
        # remove special parse_*() from the operators
        for op in [parse_list, parse_link]:
            operators.remove(op)
        return operators

    # strip '\r'
    text = text.replace("\r", "")
    # parse_code must be in front of parse_list
    code_parser = CodeParser()
    text = code_parser.code_to_code_id(text)
    text = parse_list(text)

    # simple parse_*
    for op in get_simple_parser():
        text = op(text)
    # parse_link returns a tuple
    text, link_dict = parse_link(text)

    text = code_parser.restore(text)

    if save_link_dict:
        link.save_dict(link_dict)

    return text
